<?php

abstract class Controller {

    /**
     * 默认执行的控制器
     *
     */
    const DEFAULT_CONTROLLER = 'index';

    /**
     * 默认执行的动作
     *
     */
    const DEFAULT_ACTION = 'index';

    /**
     * 是否自动解析视图
     *
     * @var boolean
     */
    private $_viewAutoRender = true;

    /**
     * 视图文件相对路径
     * 
     * @var string
     */
    private $_viewPath = '';

    /**
     * 视图变量
     * 可直接通过 $this->vars[] 写入和读取变量
     * @var array 
     */
    protected $vars = array();

    /**
     * 请求对象
     *
     * @var Request
     */
    protected $request;

    /**
     * 筛选器前缀
     * 
     * 使用该前缀的请求参数将回写到视图变量，并附加到分页链接url参数里面
     */
    protected $filter_flag = 'filter_';

    /**
     * 默认分页大小
     */
    protected $page_size = 10;

    /**
     * 控制器初始化
     */
    public function __construct($request = null) {
        if (!$request) {
            $request = Request::getInstance();
        }
        $this->request = $request;
    }

    /**
     * 自动运行,可以在自定义控制器中重载
     */
    public function init() {
        
    }

    /**
     * 设置是否自动解析视图
     *
     * @param boolean $bool
     */
    public function setViewAutoRender($bool) {
        $this->_viewAutoRender = $bool;
    }

    /**
     * 判断是否自动解析视图
     *
     */
    public function viewIsAutoRender() {
        return $this->_viewAutoRender;
    }

    /**
     * 设置变量到视图
     * 建议用assign或 $this->vars[] 写入和读取变量，避免使用魔术方法，可提高性能
     * @param string $key 键名
     * @param mixed $val 数据
     * @return void
     */
    public function __set($key, $val) {
        $this->vars[$key] = $val;
    }

    /**
     * 得到视图变量
     * 建议用assign或 $this->vars[] 写入和读取变量，避免使用魔术方法，可提高性能
     * @param string|array $key 键名或数据
     * @param mixed $val 数据
     * @return void
     */
    public function __get($key) {
        return isset($this->vars[$key]) ? $this->vars[$key] : null;
    }

    /**
     * 设置视图变量，也可以直接使用$this->变量名来设置，参考__get和__set方法
     * 建议用assign或 $this->vars[] 写入和读取变量，避免使用魔术方法，可提高性能
     * @param type $key
     * @param type $value
     */
    public function assign($key, $value = null) {
        if (is_array($key)) {
            $this->vars = array_merge($this->vars, $key);
        } else {
            $this->vars[$key] = $value;
        }
    }

    /**
     * 输出json
     * @param type $code
     * @param string|array $data
     */
    public function json($code, $data = array()) {
        ob_clean();
        header('Access-Control-Allow-Origin: *');
        header('Access-Control-Allow-Methods: POST,GET,OPTIONS,DELETE');
        header('Access-Control-Allow-Headers: x-requested-with,content-type');
        header("Content-Type:" . \Http::mimeType('json') . "; charset=" . \Sys::getCfg('charset'));
        http_response_code(200);

        if (is_array($data)) {
            array_pretreat($data);
            $resp = array_merge($data, array('code' => $code));
        } else {
            $resp = array(
                'code' => $code,
                'msg' => $data,
            );
        }
        die(json_encode($resp));
    }

    /**
     * API方式下，向客户端输出JSON数据，附加错误提示信息
     */
    public function fail($msg = '') {
        $this->assign('code', '-1');
        $this->assign('msg', $msg);
        $this->json(-1, $this->vars);
    }

    /**
     * API方式下，向客户端输出JSON数据，附加提示信息
     */
    public function succ($msg = '') {
        $this->assign('code', '1');
        $this->assign('msg', $msg);
        $this->json(1, $this->vars);
    }

    /**
     * 直接显示视图
     * @param type $filename
     */
    public function display($filename = '') {
        $this->setViewAutoRender(false);
        if ($this->vars) {
            extract($this->vars);
        }

        if (Sys::getCfg('debug')) {

            $registry = Registry::getInstance();
            $viewData = $registry->get('VIEW_DEBUG');
            $viewData[$filename] = $this->_vars;
            $registry->set('VIEW_DEBUG', $viewData);
        }

        include $this->template($filename);
    }

    /**
     * 获取渲染后的视图内容
     * @param type $filename
     * @return type
     */
    public function render($filename = '') {
        ob_start();
        $this->display($filename);
        return ob_get_clean();
    }

    /**
     * 设置视图文件相对路径
     * 设置后的路径相当于 APP_PATH . '/' . MODULE_NAME . '/Template/' 加上$path参数
     */
    public function setViewPath($path = '') {
        if ($path) {
            $this->_viewPath = trim($path, '/') . '/';
        } else {
            $this->_viewPath = '';
        }
    }

    /**
     * 编译视图模板文件
     * @param string $filename
     * @return string 生成的视图文件路径
     */
    public function template($filename) {

        if (!$filename) {
            $filename = $this->request->getAction();
        }

        // 获取控制器名
        $controller = $this->request->getController();

        // 生成视图文件路径
        $source = APP_PATH . '/' . MODULE_NAME . '/Template/' . $this->_viewPath . $controller . '/' . $filename . '.html';
        $compile = DATA_PATH . '/Template/' . MODULE_NAME . '/' . $this->_viewPath . $controller . '/' . $filename . '.php';

        $source = str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $source);
        $compile = str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $compile);

        if (is_file($source)) {
            if (Sys::getCfg('debug') || !is_file($compile) || filemtime($source) > filemtime($compile)) {
                Template::template_compile($source, $compile);
            }
            return $compile;
        } else {
            die('视图模板文件不存在：' . $source);
        }
    }

    /**
     * 服务器端或客户端重定向
     */
    public function location($url, $client = false, $top = false) {
        if ($client) {
            if ($top) {
                die('<script type="text/javascript">top.location.replace("' . http_url_decode($url) . '")</script>');
            } else {
                die('<script type="text/javascript">location.replace("' . http_url_decode($url) . '")</script>');
            }
        }
        ob_end_clean();
        header("Location: $url");
        exit(0);
    }

    /**
     * 构建URL并跳转
     */
    public function gotoUrl($controller, $action = '', $params = array()) {
        $url = $this->createUrl($controller, $action, $params);
        $this->location($url);
    }

    /**
     * 构建URL
     * 
     * @param string $controller 控制器
     * @param string $action 动作
     * @param array $params 参数
     * @param boolean $transform 是否把参数转换为/分割模式
     * @param string $module 模块分组
     * @return string
     */
    public function createUrl($controller, $action = self::DEFAULT_ACTION, $params = null, $transform = true, $module = null) {

        if (!$action) {
            $action = 'index';
        }

        return Sys::url($controller, $action, $params, $transform, $module);
    }

    /**
     * 创建分页
     * @return array
     */
    protected function _createPager($count, $params = array(), $options = array()) {

        /* 解析过滤参数 */
        $filters = array();

        if ($this->filter_flag) {
            $filter = $this->request->get();
            foreach ($filter as $key => $value) {
                if (strpos($key, $this->filter_flag) === 0 && isset($value)) {
                    if (is_string($value)) {
                        $filters[$key] = trim(urldecode(urldecode($value)));
                        // 解码，是因为浏览器会对中文进行编码
                        $this->assign($key, trim(urldecode(urldecode($value))));
                    }
                    if (is_array($value)) {
                        $filters[$key] = $value;
                        $this->assign($key, $value);
                    }
                }
            }
        }

        $url = $this->createUrl($this->request->getController(), $this->request->getAction());

        if (is_array($params) && count($params)) {
            $params = array_merge($filters, $params);
        } else {
            $params = $filters;
        }

        if ($params) {
            $url .= '?' . http_build_query($params);
        }

        $pager = new Page();

        $options = array_merge(array(
            'perpage' => $this->page_size,
            'count' => $count,
            'url' => $url), $options);

        $pager->set($options);

        $result = $pager->result();

        $length = $pager->get('perpage');

        $offset = ($result['curpage'] - 1) * $length;

        $data = array_merge($result, array(
            'offset' => $offset,
            'length' => $length,
            'url' => $url,
        ));

        return $data;
    }

}
